﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Algorithms.Expressions
{
    public class ExpressionFactoryContainer
    {
        #region Statics

        public static readonly ParameterExpression DefaultInputParameterExpression = Expression.Parameter(typeof(double), "x");

        #endregion // Statics

        #region Fields

        private SortedDictionary<int, List<IExpressionFactory>> _expressionFactoryDictionary;

        #endregion // Fields

        #region Constructor

        public ExpressionFactoryContainer()
        {
            _expressionFactoryDictionary = new SortedDictionary<int, List<IExpressionFactory>>();
        }

        #endregion // Constructor

        #region Properties

        public IEnumerable<IExpressionFactory> Factories
        {
            get
            {
                return from dictionaryEntry in _expressionFactoryDictionary
                       from expressionFactory in dictionaryEntry.Value
                       select expressionFactory;
            }
        }

        #endregion // Properties

        #region Methods

        public void RemoveExpressionFactory(IExpressionFactory factory)
        {
            if (factory == null)
                throw new ArgumentNullException("factory");

            int requiredArgumentLength = factory.RequiredArgumentLength;

            if (_expressionFactoryDictionary.ContainsKey(requiredArgumentLength))
            {
                List<IExpressionFactory> expressionFactoryList = _expressionFactoryDictionary[requiredArgumentLength];
                expressionFactoryList.Remove(factory);
            }
        }

        public void AddExpressionFactory(IExpressionFactory factory)
        {
            if (factory == null)
                throw new ArgumentNullException("factory");

            int factoryArgumentLength = factory.RequiredArgumentLength;

            if (!_expressionFactoryDictionary.ContainsKey(factoryArgumentLength))
            {
                _expressionFactoryDictionary[factoryArgumentLength] = new List<IExpressionFactory>();
            }

            _expressionFactoryDictionary[factoryArgumentLength].Add(factory);
        }

        public void AddExpressionFactories(IEnumerable<IExpressionFactory> factories)
        {
            if (factories == null)
                throw new ArgumentNullException("factories");

            foreach (IExpressionFactory factory in factories)
            {
                AddExpressionFactory(factory);
            }
        }

        public IExpressionFactory FindExpressionFactory(int input, int argumentLimit, int expressionsLeft)
        {
            int expressionFactoryDictionaryLength = _expressionFactoryDictionary.Count;
            int expressionFactoryMinimalIndex = 0;
            
            // since we use modular operations, we can only use positive input values
            uint positiveInput = (uint)input;

            // keep on schrinking the max factoryListIndex untill it fits with the argumentLimit
            // todo: a little performance can be gained by not using linq but cache the dictionary in a array and retrieve the element by indexing
            while (_expressionFactoryDictionary.Keys.ElementAt(expressionFactoryDictionaryLength - 1) > argumentLimit)
            {
                expressionFactoryDictionaryLength--;
            }

            // get the heighest number of arguments that can be handeled after this expression have been handeled
            int highestArgumentsHandler = _expressionFactoryDictionary.Keys.Last();
            int maximumArgumentsForHandle = highestArgumentsHandler * expressionsLeft;
            int requiredArgumentsToHandle = argumentLimit + expressionsLeft;

            // Keep increasing the minimal index untill we know for sure we can build a complete expression
            while (maximumArgumentsForHandle - (highestArgumentsHandler - _expressionFactoryDictionary.Keys.ElementAt(expressionFactoryMinimalIndex)) < requiredArgumentsToHandle)
            {
                expressionFactoryMinimalIndex++;
            }

            int expressionFactoryDictionaryIndex = CustomMath.ToRange(positiveInput, expressionFactoryMinimalIndex, expressionFactoryDictionaryLength - 1);

            List<IExpressionFactory> expressionFactoryList = _expressionFactoryDictionary.Values.ElementAt(expressionFactoryDictionaryIndex);

            int expressionFactoryListLength = expressionFactoryList.Count;
            int expressionFactoryListIndex = CustomMath.ToRange(positiveInput, expressionFactoryListLength - 1);

            IExpressionFactory result = expressionFactoryList[expressionFactoryListIndex];

            return result;
        }

        #endregion // Methods
    }
}
